package com.novel.disk.service.impl;

import com.novel.disk.common.FileUtils;
import com.novel.disk.common.MimeTypeUtils;
import com.novel.disk.entity.FileEntity;
import com.novel.disk.entity.StorageEntity;
import com.novel.disk.entity.vo.TreeData;
import com.novel.disk.framework.config.Resource;
import com.novel.disk.framework.exception.BusinessException;
import com.novel.disk.repository.FileRepository;
import com.novel.disk.service.FileService;
import com.novel.disk.service.ResourceService;
import com.novel.disk.service.StorageService;
import lombok.SneakyThrows;
import org.springframework.beans.BeanUtils;
import org.springframework.data.domain.Example;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import javax.validation.constraints.NotNull;
import java.io.File;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;

/**
 * 文件服务实现
 *
 * @author novel
 * @date 2020/7/13
 */
@Service
public class FileServiceImpl implements FileService {

    private final FileRepository fileRepository;
    private final ResourceService resourceService;
    private final StorageService storageService;

    public FileServiceImpl(FileRepository fileRepository, ResourceService resourceService, StorageService storageService) {
        this.fileRepository = fileRepository;
        this.resourceService = resourceService;
        this.storageService = storageService;
    }


    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean mkdir(String dirName, Long pid, Long userId) {
        String fileName = "";
        String path = "";
        if (pid != null && pid > 0) {
            Optional<FileEntity> entityOptional = fileRepository.findById(pid);
            if (entityOptional.isPresent()) {
                FileEntity fileEntity = entityOptional.get();
                if (fileEntity.getIsDir()) {
                    fileName += fileEntity.getFileName() + "/" + dirName;
                    path += fileEntity.getFilePath() + "/" + System.currentTimeMillis();
                } else {
                    fileName += "/" + dirName;
                    path += "/" + System.currentTimeMillis();
                }
            } else {
                fileName += "/" + dirName;
                path += "/" + System.currentTimeMillis();
            }
        } else {
            fileName += "/" + dirName;
            path += "/" + System.currentTimeMillis();
        }

        resourceService.mkdir(userId + "/" + path);
        FileEntity entity = new FileEntity();
        entity.setIsDir(true);
        entity.setFileName(fileName);
        entity.setLastModifyTime(new Date());
        entity.setCreateTime(new Date());
        entity.setPid(pid);
        entity.setFilePath(path);
        entity.setUserId(userId);
        fileRepository.save(entity);
        return true;
    }

    @Override
    public List<FileEntity> listDir(FileEntity fileEntity) {
        if (fileEntity.getPid() != null) {
            Optional<FileEntity> optional = fileRepository.findById(fileEntity.getPid());
            if (optional.isPresent()) {
                FileEntity entity = optional.get();
                return fileRepository.findAll(fileEntity.getPid(), fileEntity.getUserId(), fileEntity.getType(), entity.getFileName(), fileEntity.getFileName());
            }
        }
        return fileRepository.findAll(fileEntity.getPid(), fileEntity.getUserId(), fileEntity.getType(), "", fileEntity.getFileName());
    }

    @SneakyThrows
    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean upLoad(@NotNull MultipartFile file, Long pid, Long userId) {
        if (file == null) {
            return false;
        }
        String filename = file.getOriginalFilename();
        String md5 = FileUtils.getMD5(file.getBytes());
        String fileSuffix = FileUtils.getFileSuffix(Objects.requireNonNull(filename));
        String fileName = "", filePath = "";
        if (pid != null && pid > 0) {
            Optional<FileEntity> dir = fileRepository.findById(pid);
            if (dir.isPresent()) {
                FileEntity fileEntity = dir.get();
                if (fileEntity.getIsDir()) {
                    fileName += fileEntity.getFileName() + "/" + filename;
                    filePath += fileEntity.getFilePath() + "/" + md5 + "." + fileSuffix;
                } else {
                    fileName += "/" + filename;
                    filePath += "/" + md5 + "." + fileSuffix;
                }
            } else {
                fileName += "/" + filename;
                filePath += "/" + md5 + "." + fileSuffix;
            }
        } else {
            fileName += "/" + filename;
            filePath += "/" + md5 + "." + fileSuffix;
        }

        resourceService.upLoad(file.getInputStream(), userId + "/" + filePath);
        FileEntity fileEntity = new FileEntity();
        fileEntity.setPid(pid);
        fileEntity.setFilePath(filePath);
        fileEntity.setCreateTime(new Date());
        fileEntity.setLastModifyTime(new Date());
        fileEntity.setFileName(fileName);
        fileEntity.setIsDir(false);
        fileEntity.setHash(md5);
        fileEntity.setExtendName(fileSuffix);
        fileEntity.setFileSize(file.getSize());
        fileEntity.setDownloadNum(0);
        fileEntity.setUserId(userId);
        fileEntity.setType(MimeTypeUtils.getFileType(fileSuffix));
        fileRepository.save(fileEntity);
        synchronized (FileServiceImpl.class) {
            StorageEntity storage = storageService.getStorageByUserId(userId);
            if (storage == null) {
                storage = new StorageEntity();
                storage.setUsedSize(0L);
                storage.setTotalSize(Resource.getStorageTotalSize());
                storage.setUserId(userId);
            }
            if (fileEntity.getFileSize() != null) {
                storage.setUsedSize(storage.getUsedSize() + fileEntity.getFileSize());
            }
            storageService.updateStorage(storage);
        }
        return true;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean remove(Long[] sourceListId, Long userId) {
        List<FileEntity> fileEntityList = fileRepository.findAllById(Arrays.asList(sourceListId));

        Long fileSize = 0L;
        for (FileEntity fileEntity : fileEntityList) {
            if (fileEntity.getUserId() != null && fileEntity.getUserId().equals(userId)) {
                resourceService.remove(userId + "/" + fileEntity.getFilePath());
                List<FileEntity> entities = fileRepository.findAllByFilePathIsStartingWith(fileEntity.getFilePath());
                for (FileEntity entity : entities) {
                    if (entity.getFileSize() != null) {
                        fileSize += entity.getFileSize();
                    }
                }
                fileRepository.deleteInBatch(entities);
            }
        }
        synchronized (FileServiceImpl.class) {
            StorageEntity storage = storageService.getStorageByUserId(userId);
            if (storage == null) {
                storage = new StorageEntity();
                storage.setUsedSize(0L);
                storage.setTotalSize(Resource.getStorageTotalSize());
                storage.setUserId(userId);
            }
            storage.setUsedSize(storage.getUsedSize() - fileSize);
            storageService.updateStorage(storage);
        }
        return true;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean rename(Long id, String name, Long userId) {
        Optional<FileEntity> optionalFileEntity = fileRepository.findById(id);
        if (optionalFileEntity.isPresent()) {
            FileEntity fileEntity = optionalFileEntity.get();
            if (fileEntity.getUserId() != null && fileEntity.getUserId().equals(userId)) {
                FileEntity pFileEntity = fileEntity.getPFileEntity();
                String pname = pFileEntity == null ? "" : pFileEntity.getFileName();
                if (!fileEntity.getIsDir()) {
                    name = pname + "/" + name + "." + fileEntity.getExtendName();
                } else {
                    name = pname + "/" + name;
                }
                fileEntity.setFileName(name);
                fileRepository.save(fileEntity);
            }
        }
        return true;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean copyDir(Long[] sourceListId, Long targetId, Long userId) {
        List<FileEntity> fileEntityList = fileRepository.findAllById(Arrays.asList(sourceListId));
        AtomicReference<Long> fileSize = new AtomicReference<>(0L);
        //判断这些文件夹文件的大小
        for (FileEntity fileEntity : fileEntityList) {
            List<FileEntity> fileEntities = fileRepository.findAllByFilePathIsStartingWith(fileEntity.getFilePath());
            fileEntities.forEach(it -> {
                if (it.getFileSize() != null) {
                    fileSize.updateAndGet(v -> v + it.getFileSize());
                }
            });
        }

        StorageEntity storage = storageService.getStorageByUserId(userId);
        if (fileSize.get() > (storage.getTotalSize() - storage.getUsedSize())) {
            throw new BusinessException("存储空间不足！", 500);
        }
        if (targetId == 0) {
            FileEntity fileEntity = new FileEntity();
            fileEntity.setFileId(targetId);
            fileEntity.setUserId(userId);
            copyDir(fileEntityList, fileEntity, userId);
        } else {
            Optional<FileEntity> entityOptional = fileRepository.findById(targetId);
            entityOptional.ifPresent(fileEntity -> copyDir(fileEntityList, fileEntity, userId));
        }
        return true;
    }


    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean moveDir(Long[] sourceListId, Long targetId, Long userId) {
        List<FileEntity> fileEntityList = fileRepository.findAllById(Arrays.asList(sourceListId));
        if (targetId == 0) {
            FileEntity fileEntity = new FileEntity();
            fileEntity.setFileId(targetId);
            fileEntity.setUserId(userId);
            moveDir(fileEntityList, fileEntity, userId);
        } else {
            Optional<FileEntity> entityOptional = fileRepository.findById(targetId);
            entityOptional.ifPresent(fileEntity -> moveDir(fileEntityList, fileEntity, userId));
        }
        return true;
    }


    @Override
    public byte[] download(Long fileId) {
        Optional<FileEntity> entityOptional = fileRepository.findById(fileId);
        if (entityOptional.isPresent()) {
            FileEntity entity = entityOptional.get();
            entity.setDownloadNum(entity.getDownloadNum() != null && entity.getDownloadNum() >= 0 ? entity.getDownloadNum() + 1 : 1);
            fileRepository.save(entity);
            return resourceService.download(entity);
        }
        return null;
    }

    @Override
    public File readBytes(String path) {
        return resourceService.readBytes(path);
    }

    @Override
    public TreeData listDirTree(Long userId) {
        FileEntity fileEntity = new FileEntity();
        fileEntity.setIsDir(true);
        fileEntity.setUserId(userId);
        List<FileEntity> all = fileRepository.findAll(Example.of(fileEntity));
        TreeData treeData = new TreeData();
        treeData.setId(0L);
        treeData.setLabel("/");
        treeData.setChildren(getTreeData(all, 0));
        return treeData;
    }

    @Override
    public FileEntity getFileById(Long fileId) {
        return fileRepository.findById(fileId).orElse(null);
    }

    @Override
    public FileEntity getDownloadUrl(Long fileId) {
        Optional<FileEntity> entityOptional = fileRepository.findById(fileId);
        if (entityOptional.isPresent()) {
            FileEntity fileEntity = entityOptional.get();
            if (fileEntity.getIsDir()) {
                String path = resourceService.zip(fileEntity);
                fileEntity.setUrl(path);
            } else {
                fileEntity.setUrl(fileEntity.getUserId() + fileEntity.getFilePath());
            }
            return fileEntity;
        }
        return null;
    }

    @Override
    public boolean unzip(Long fileId, Long userId) {
        Optional<FileEntity> entityOptional = fileRepository.findById(fileId);
        if (entityOptional.isPresent()) {
            FileEntity fileEntity = entityOptional.get();
            StorageEntity storage = storageService.getStorageByUserId(userId);
            if (storage == null || fileEntity.getFileSize() > (storage.getTotalSize() - storage.getUsedSize())) {
                throw new BusinessException("存储空间不足！", 500);
            }
            synchronized (FileServiceImpl.class) {
                //1.解压
                File unzip = resourceService.unzip(fileEntity);
                if (unzip == null) {
                    return false;
                }
                //2.扫描文件，添加到数据库
                FileEntity entity = getFileEntity(fileEntity, unzip, userId);
                entity.setPid(fileEntity.getPid());
                fileRepository.save(entity);
                resourceService.remove(unzip);

                if (fileEntity.getFileSize() != null) {
                    storage.setUsedSize(storage.getUsedSize() + fileEntity.getFileSize());
                }
                storageService.updateStorage(storage);
            }
        }
        return true;
    }

    @SneakyThrows
    public FileEntity getFileEntity(FileEntity fileEntity, File unzip, Long userId) {
        FileEntity entity = new FileEntity();
        entity.setDownloadNum(0);

        entity.setUserId(userId);
        //父目录的路径  父目录名称
        String pPath = "", pName = "";

        if (fileEntity != null && fileEntity.getIsDir()) {
            pPath += fileEntity.getFilePath();
            pName += fileEntity.getFileName();
        } else if (fileEntity != null && !fileEntity.getIsDir()) {
            //如果目录时文件，那么目录就应该为文件的上级目录

            pName += fileEntity.getFileName().replace(fileEntity.getName(), "");
            pPath += fileEntity.getFilePath().replace(fileEntity.getPath(), "");
        }

        if (unzip.isDirectory()) {
            entity.setIsDir(true);
            File[] files = unzip.listFiles();

            //当前新目录的路径
            String filePath = pPath + "/" + System.currentTimeMillis();
            //当前父目录名称
            String fileName;
            if (fileEntity != null && !fileEntity.getIsDir()) {
                String name = fileEntity.getName();
                fileName = pName + "/" + name.substring(0, name.lastIndexOf("."));
            } else {
                fileName = pName + "/" + unzip.getName();
            }


            //创建目录
            resourceService.mkdir(userId + "/" + filePath);
            entity.setFilePath(filePath);
            entity.setFileName(fileName);

            if (files != null && files.length > 0) {
                ArrayList<FileEntity> fileEntities = new ArrayList<>(files.length);
                for (File file : files) {
                    FileEntity fileEntity1 = getFileEntity(entity, file, userId);
                    fileEntities.add(fileEntity1);
                }
                entity.setFileEntityList(fileEntities);
            }
        } else {
            String fileSuffix = FileUtils.getFileSuffix(Objects.requireNonNull(unzip.getName()));
            String md5 = FileUtils.getMD5(unzip);
            //当前新目录的路径
            String filePath = pPath + "/" + md5 + "_" + unzip.getName() + "." + fileSuffix;
            //当前父目录名称
            String fileName = pName + "/" + unzip.getName();

            //移动文件到指定目录
            resourceService.moveDir(unzip, userId + "/" + filePath);

            entity.setFilePath(filePath);
            entity.setFileName(fileName);

            entity.setIsDir(false);
            entity.setHash(md5);
            entity.setFileSize(unzip.length());

            entity.setType(MimeTypeUtils.getFileType(fileSuffix));
            entity.setExtendName(fileSuffix);
        }
        return entity;
    }


    /**
     * 树结构
     *
     * @param list 列表
     * @param pid  父级id
     * @return 结果
     */
    private List<TreeData> getTreeData(List<FileEntity> list, long pid) {
        List<TreeData> returnList = new ArrayList<>();
        for (FileEntity t : list) {
            if (t.getPid() == pid) {
                TreeData treeData = new TreeData();
                treeData.setId(t.getFileId());
                treeData.setLabel(t.getName());
                returnList.add(treeData);
                List<TreeData> child = getTreeData(list, t.getFileId());
                treeData.setChildren(child);
            }
        }
        return returnList;
    }

    /**
     * 复制
     *
     * @param fileEntityList   复制文件列表
     * @param targetFileEntity 目标目录
     * @param userId           用户id
     * @return 结果
     */
    private boolean copyDir(List<FileEntity> fileEntityList, FileEntity targetFileEntity, Long userId) {
        Long fileSize = 0L;
        for (FileEntity fileEntity : fileEntityList) {
            FileEntity pFileEntity = copy(targetFileEntity, fileEntity, userId);
            if (fileEntity.getIsDir()) {
                //文件夹，递归复制
                if (pFileEntity != null) {
                    //把对应的文件移动到当前目录下
                    copyDir(fileEntity.getFileEntityList(), pFileEntity, userId);
                }
            } else {
                //文件则直接复制
                if (pFileEntity != null && pFileEntity.getFileSize() != null) {
                    fileSize += pFileEntity.getFileSize();
                }
            }
        }
        synchronized (FileServiceImpl.class) {
            StorageEntity storage = storageService.getStorageByUserId(userId);
            if (storage == null) {
                storage = new StorageEntity();
                storage.setUsedSize(0L);
                storage.setTotalSize(Resource.getStorageTotalSize());
                storage.setUserId(userId);
            }
            storage.setUsedSize(storage.getUsedSize() + fileSize);
            storageService.updateStorage(storage);
        }
        return true;
    }

    /**
     * 复制
     *
     * @param targetFileEntity 目标目录
     * @param fileEntity       文件
     * @return 结果
     */
    private FileEntity copy(FileEntity targetFileEntity, FileEntity fileEntity, Long userId) {
        if (targetFileEntity.getUserId() != null && targetFileEntity.getUserId().equals(userId) && fileEntity.getUserId() != null && fileEntity.getUserId().equals(userId)) {
            FileEntity newFileEntity = new FileEntity();
            BeanUtils.copyProperties(fileEntity, newFileEntity, "fileName", "pid", "filePath", "fileId", "fileEntityList", "pFileEntity");
            String filePath = targetFileEntity.getFilePath() == null ? "/" + fileEntity.getPath() : targetFileEntity.getFilePath() + "/" + fileEntity.getPath();
            String fileName = targetFileEntity.getFileName() == null ? "/" + fileEntity.getName() : targetFileEntity.getFileName() + "/" + fileEntity.getName();
            newFileEntity.setPid(targetFileEntity.getFileId());
            resourceService.copyDir(userId + "/" + fileEntity.getFilePath(), userId + "/" + filePath);
            newFileEntity.setFilePath(filePath);
            newFileEntity.setFileName(fileName);
            return fileRepository.save(newFileEntity);
        }
        return null;
    }

    /**
     * 移动文件
     *
     * @param fileEntityList   文件列表
     * @param targetFileEntity 目标目录
     * @param userId           用户id
     * @return 结果
     */
    private boolean moveDir(List<FileEntity> fileEntityList, FileEntity targetFileEntity, Long userId) {
        for (FileEntity fileEntity : fileEntityList) {
            if (fileEntity.getIsDir()) {
                //文件夹，递归移动
                move(targetFileEntity, fileEntity, userId);
                //把对应的文件移动到当前目录下
                moveDir(fileEntity.getFileEntityList(), fileEntity, userId);
            } else {
                //文件则直接移动
                move(targetFileEntity, fileEntity, userId);
            }
        }
        return true;
    }

    private void move(FileEntity targetFileEntity, FileEntity fileEntity, Long userId) {
        if (targetFileEntity.getUserId() != null && targetFileEntity.getUserId().equals(userId) && fileEntity.getUserId() != null && fileEntity.getUserId().equals(userId)) {
            String filePath = targetFileEntity.getFilePath() == null ? "/" + fileEntity.getPath() : targetFileEntity.getFilePath() + "/" + fileEntity.getPath();
            String fileName = targetFileEntity.getFileName() == null ? "/" + fileEntity.getName() : targetFileEntity.getFileName() + "/" + fileEntity.getName();
            fileEntity.setPid(targetFileEntity.getFileId());
            resourceService.moveDir(userId + "/" + fileEntity.getFilePath(), userId + "/" + filePath);
            fileEntity.setFilePath(filePath);
            fileEntity.setFileName(fileName);
            fileRepository.saveAndFlush(fileEntity);
        }
    }
}
